package org.fjsei.yewu;

import io.camunda.spring.client.annotation.Deployment;
import io.camunda.zeebe.client.api.response.ProcessInstanceEvent;
//import io.camunda.zeebe.spring.client.CamundaAutoConfiguration;
//import io.camunda.zeebe.spring.client.EnableZeebeClient;
//import io.camunda.zeebe.spring.client.annotation.ZeebeDeployment;
//import io.camunda.zeebe.spring.client.annotation.Deployment;
//import io.camunda.zeebe.spring.client.lifecycle.ZeebeClientLifecycle;
import lombok.extern.slf4j.Slf4j;
import md.specialEqp.Equipments;
import md.specialEqp.inspect.IspRepository;
import md.specialEqp.inspect.TaskRepository;
import org.fjsei.yewu.config.property.FileStorageProperties;
import org.fjsei.yewu.service.JpaService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.*;
import org.springframework.boot.builder.SpringApplicationBuilder;

/*
主程序、启动类; 相当于API网关。
集成ehcache二级缓存 https://blog.csdn.net/yiduyangyi/article/details/54176453
SpringBootApplication(exclude = {})随着CRDB增加;
EnableProcessApplication对应的配置META-INF/processes.xml file.
*/

///@EnableCaching  属于spring自带cache机制。
//@EnableTransactionManagement(order = Ordered.LOWEST_PRECEDENCE - 1) // Bump up one level to enable extra advisors
//@EnableZeebeClient
//【流程引擎关闭】application-NoZeb.yml  exclude: 加上CamundaAutoConfiguration
@Slf4j
@Deployment(resources = "classpath*:/bpmn/**/*.bpmn")    //20240404:不开启流程模块
@EnableScheduling
@EnableAsync
@EnableCaching
@EnableConfigurationProperties({
        FileStorageProperties.class
})
@EnableAspectJAutoProxy(proxyTargetClass = true)
@SpringBootApplication(exclude = {
      TransactionAutoConfiguration.class,
      DataSourceTransactionManagerAutoConfiguration.class,
      DataSourceAutoConfiguration.class
})
@ComponentScan({"md","org.fjsei.yewu"})
@ServletComponentScan
@EnableTransactionManagement(proxyTargetClass = true)
public class StartApplication implements CommandLineRunner{
  private final Logger logger = LoggerFactory.getLogger(StartApplication.class);

//  @Autowired  private ZeebeClientLifecycle client;
  /* 也是单独组件啊,根据名字来匹配的！； 配置文件对应camunda:delegateExpression="${sayHelloDelegate}"
    在taskService.complete(‘UserTask_1’)+onTaskEvent("complete")完成以后流程调用这里的：
    流程图上面的第二个框框对应的:id=ServiceTask_1自动任务： name='say hello' #详细/实现="${sayHelloDelegate}"
  * */
  //@Bean  public JavaDelegate sayHelloDelegate() {  }
  //@Autowired  private ProcessEngine processEngine;      //@Autowired出现：提示问题爆红?但是运行正常的。
  //@Resource  private ProcessEngine processEngine;        //@Resource不是Spring?

  @Autowired
  private ConfigurableApplicationContext context;

    @Deprecated
  @Autowired  private JpaService jpaService;     //测试用途的


/*  SampleProcess流程结束呢
  public boolean isProcessInstanceFinished() {
    final HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
            .processInstanceId(sampleProcess.getProcessInstanceId()).singleResult();
    return historicProcessInstance != null && historicProcessInstance.getEndTime() != null;
  }*/

  //轮询测试@Test: 固定60秒定时器fixedDelay = 60000L，触发执行的。
  //@Scheduled(fixedDelay = 80000L)  //测试
//    @Deprecated
//  public void exitApplicationWhenProcessIsFinished() {
//    if (!client.isRunning())    return;
//    final ProcessInstanceEvent event =
//            client.newCreateInstanceCommand()
//                    .bpmnProcessId("demoProcess")
//                    .latestVersion()
//                    .variables("{\"a\": \"" + UUID.randomUUID().toString() + "\",\"b\": \"" + new Date().toString() + "\"}")
//                    .send()
//                    .join();
//    logger.info("started instance for workflowKey='{}', bpmnProcessId='{}', version='{}' with workflowInstanceKey='{}'",
//            event.getProcessDefinitionKey(), event.getBpmnProcessId(), event.getVersion(), event.getProcessInstanceKey());
//  }

  //只运行一次啊:
  @Override
  public void run(String... strings) {
    //logger.info("主程序,CommandLineRunner#run() - {}", ToStringBuilder.reflectionToString(application, ToStringStyle.SHORT_PREFIX_STYLE));
    //jpaService.testIt();    // jpaService.upsertAll(2500);
  }

  public static void main(String[] args) {
    try {
      SpringApplication.run(StartApplication.class, args);
    } catch (ApplicationContextException e) {
      log.info("启动失败：{}", e.getMessage());      //无法正常运行，也无法自动停掉本进程的：zeebe被挂住?
      System.exit(-1);          //JVM异常中止！
        //SpringApplication.exit(null);
    }
  }


/* jpa懒加载异常：OSIV: LAZY association配置加jpa.properties.open-in-view: true; #有性能缺陷。https://blog.csdn.net/xiaohe00happy/article/details/108237543
  @Bean
  public OpenEntityManagerInViewFilter openEntityManagerInViewFilter(){
    return new OpenEntityManagerInViewFilter();
  }         //递归类 FetchType.LAZY 典型N + 1问题 https://www.cnblogs.com/ealenxie/p/9800818.html
  GRAPHQL_SPRING_BOOT 12版?自动open-in-view；   我改成open-in-view: false出现报错
  */

  @PersistenceContext(unitName = "entityManagerFactorySei")
  private EntityManager emSei;

  /*
  启动后的可执行的定制任务类。Override  run();
  用@Order 注解来定义执行顺序。
      */
  @Bean
  @Transactional
  public CommandLineRunner demo(Equipments eqpRepository, TaskRepository  taskRepository, IspRepository ispRepository)
  {
    if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
    return (args) -> {
      //Author author = new Author();   Started StartApplication in 133 seconds
        logger.info("mainProject special-equipment-backend 后端启动好了!");
    };
  }

 /**把定时任务变多线程执行：
 * 在启动类中配置TaskScheduler线程池大小; @Scheduled(cron = "0 0/1 * * * ?")//每一分钟执行一次
 */
  @Bean
  public TaskScheduler taskScheduler() {
    ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
    taskScheduler.setPoolSize(20);    //线程池多大=20
    return taskScheduler;
  }
}



/* 还有这样搞的：   org/apache/shardingsphere/example/sharding/spring/boot/jpa/ExampleMain.java
@ComponentScan("org.apache.shardingsphere.example.core.jpa")
@EntityScan(basePackages = "org.apache.shardingsphere.example.core.jpa.entity")
@SpringBootApplication(exclude = JtaAutoConfiguration.class)引入 shardingsphere-transaction-xa-core 后，如何避免 spring-boot 自动加载默认的 JtaTransactionManager
public class ExampleMain {
    public static void main(final String[] args) throws SQLException {
        try (ConfigurableApplicationContext applicationContext = SpringApplication.run(ExampleMain.class, args)) {
            ExampleExecuteTemplate.run(applicationContext.getBean(ExampleService.class));
        }
    }
}  测试例子的；
 */


//@某些注解比如lombok.Data是编译的用，IDEA配置Build>Compiler>Annotation Processors勾上。
//graphql-spring-boot-starter帶入缺省還有一個graphQLsevlet:  缺省endpoint配置/graphql / . 缺省配模型文件目錄的*/*.graphqls;
//安全控制核心在：org/fjsei/yewu/config/MyGraphQLWebAutoConfiguration.java ，没有jwt认证，就无法访问后端资源。
//数据库H2没有启动时，可能无法启动本服务器。
//hibernate-ehcache不支持3代的，所以出现告警WARN  org.hibernate.orm.deprecation - HHH020100:  新版本ehcache3不能用,要配套ehcache2；
//注入配置参数对象的模式@EnableConfigurationProperties， 否则启动时找不到bean；
//SpringBoot-2.1.1使用https 需要  https://www.cnblogs.com/yvanchen1992/p/10111534.html
/*Actuator:在运行时改变日志等级     https://www.jianshu.com/p/d5943e303a1f
前后端非分离架构Spring MVC当中的M存数据K/V即Model(模型)，V=模板引擎生成HTML，C=@RequestMapping("/users")。
Spring Web Flow构建于Spring MVC之上，跨多HTTP请求，具有状态，处理事务数据。　@EnableSpringDataWebSupport；
HATEOAS是REST架构约束，client不用事先知道服务步骤，不用硬编码URI；服务器还可以在不破坏和客户端交互情况下，更改URI；属Spring MVC时代。
egg框架：node.js构建后端的工具型类似Springboot的JavaScript对应物，node后端服务器，egg和graphQL是独立关系。
不是这个启动类所在的包底下的Controller目录的Rest无法访问到。
@SpringBootApplication 报错 Found multiple @SpringBootConfiguration annotated classes
启动camuda错：Table act_ge_property doesn't exist无法生成表 若MySql必须在url: jdbc:mysql://?添加上&nullCatalogMeansCurrent=true 自动创建更新数据库表
CockroachDB是一个分布式SQL数据库，建立在事务性和强一致性的键值存储上。它水平伸缩；在磁盘、机器、机架甚至数据中心故障中幸存下来，延迟中断最小，无需手动干预；支持高度一致的ACID事务；并为结构化、操作和查询数据提供了熟悉的SQL API。
jOOQ 做ORM? 需要从数据库中抽取反向生成模型类。 而mybatis需要另外维护实体表定义。
WebFlux配spring-data-r2dbc实际是位于接口层的RestController返回实体的，=异步 Mono<<Page<T>>>,一次查询很多数据才有意义吧，修改保存事务都需确认完成吧 Mono<Void> transfer()。
@EnableR2dbcRepositories 明显不是JPA+Hibernate的做法啊。 Flux<Account> findAllBy(Pageable) #需要手写/db/create.sql实体表。用'reactor-core'非阻塞。
R2dbc Flux Mono实际对等于graphQL底层中间件内部功能层次，就是在graphQL底层提供类似的异步能力。IO模型 Reactive Stream
GraphQL及元数据驱动架构在后端BFF中的实践 CompletableFuture  https://zhuanlan.zhihu.com/p/370436576
异步编程对吞吐量以及资源有好处，但是有没有统一的抽象去解决此类问题Reactive Streams java.util.concurrent.Flow。  https://zhuanlan.zhihu.com/p/352797362
Java异步编程: R2dbc简化=类同JDBC就就多了Repository和异步  https://www.jianshu.com/p/8f982883b832/
WebFlux基于reactor，数据库驱动都得换，DB链接url配置不一样:r2dbc:不是jdbc:注解@EnableR2dbcRepositories，也不用@Entity等JPA能力是简化版本ORM不走JPA；异步不一定需要reactor:Reactive Stream。
Java 9提供响应式流编程。R2dbc：都不用DataSource，也没有EntityManager，配置文件也没有连接池hikari:配置了，直接上特殊的Repository，它不提供缓存、延迟加载、或ORM框架的许多功能。
Couchbase 支持键值数据访问和全文搜索;  'Spring Data R2DBC'不好集成#数据库表要自己建,不好混合JPA一起用。
r2dbc配置多数据源 @EnableR2dbcRepositories(basePackages="",entityOperationsRef='多2') @Autowired @Qualifier("nlp")R2dbcEntityTemplate  https://blog.csdn.net/weixin_46633487/article/details/119185474
定时任务要以集群的方式运行，例如每天 23:00 执行检查任务，只需要集群中的一台运行即可，可考虑用 Quartz。
@Scheduled注解会在默认情况下以单线程的方式执行定时任务。将定时任务变成多线程执行：TaskScheduler配置@Async,@EnableAsync;
@Async导致并发就算@Scheduled注解是同一个接口函数的也可并行，只要时间调度到了。
IDEA的Database工具浏览表格或执行SQL时默认是开启事务的，有可能阻塞其它事务，特别CRDB串行化，严重阻塞其它人运行,尽量用程序来操作,限制事务时间不超过10秒之内的。
动态代理机制 proxy-target-class=true表示使用CGLib代理，如果为false就是默认使用JDK动态代理AOP https://blog.csdn.net/qq1723205668/article/details/56481476
CDI支持的：WildFly，Quarkus, Spring。框架spring-web有用，但是spring-webmvc没用到的！
cursor不适合无状态操作，例如网页显示结果!
no session报错，没分配JDBC数据库连接 单个事务 https://blog.csdn.net/xiaohe00happy/article/details/108237543 数据库连接未及时释放导致缓慢 Hikari；当
Project Loom  connection pool HikariCP； 有许多任务大部分时间处于阻塞IO时，Loom很有用。可以在虚拟线程中连接网站、数据库等,要替换运行时中的一部分堆栈;
1个HTTP request对应1个Java thread,session,socket connection,Java用户线程与内核OS线程是一对一;JDBC(是同步阻塞的)，这也是为什么数据库是瓶颈=让CPU一直在等待I/O返回。
HikariCP在Spring Data R2DBC中不可用，连接池选择使用R2DBC Pool。关于池大小：混合长时间运行的事务和非常短的事务的系统:创建两个池一个用于长时间运行的作业，另一个用于实时查询。jmeter压测工具;
异步io包括webflux适用场景是高并发高延迟，并发度小或后端服务延迟（服务IO阻塞耗时）小于10ms的情况，用异步io因为调度开销，R2DBC性能和吞吐量反而不如普通阻塞同步式io/JDBC。
webflux中的reactor目前还不支持http2,允许在一个http2 TCP连接上能真正并发多个请求，这样在高并发情况下连接池中只要不多的连接资源，也避免了高并发时等待空闲连接的窘境或新建连接的时间。
在HTTP2中多个HTTP请求可以在同一个TCP连接中并发进行。Chrome最多允许对同一个Host建立六个TCP连接。
若只是进行数据库CRUD操作，没有大量的其他远程IO操作，则可启用OSIV，这样可不用多次频繁的向链接池申请新连接。
默认OSIV启用的：您可考虑@EntityGraph()解决懒加载异常+因为实体内省或projection不同适配要各自的attributePaths字段的配置，最后只好定义多个查询Repository接口:不算好方案？另外N+1语句。
@Transactional在事务上下文中将数据库IO与其他类型的IO混合用很不好{耗时的远程调用或者本地磁盘IO=速度慢}，必须避免混搭!。OSIV会话在第一次数据库IO后一直保持连接，直到请求结束。
当禁用OSIV后，您没加@Transactional的service方法中，多次用数据库Repository接口，就会每次都要申请新的连接session{包括JPA的lazy association},
实际依然报错LazyInitializationException: no Session ? 是因为GraphQL控制器接口返回时刻内省实际是异步的；所以还是会归还Session，异步运行在不同的Session。若Rest接口不会？
抉择：非必须的但是您加了@Transactional导致事务可能时间拖长了，长时间事务带来其它问题，数据库不欢迎长时间事务：很矛盾。
OSIV open-in-view禁用尝试发现 真没感觉有优点啊！不如保持默认启用。
*/
